;;; init.el --- Initial setup.

;;; Commentary:
;; Initial setup and then executing `spruce-boot-file' and units.

;;; Code:

(when (boundp 'native-comp-eln-load-path)
  (setq native-comp-eln-load-path (list (concat (file-truename user-emacs-directory) ".jit/")
					(nth 1 native-comp-eln-load-path))
	native-comp-jit-compilation nil))

(setq gc-cons-threshold most-positive-fixnum)

(defvar user-emacs-truedirectory
  (file-truename user-emacs-directory)
  "Absolute path to `user-emacs-directory'.")

(defvar spruce-cache-truedirectory
  (concat user-emacs-truedirectory ".cache/")
  "Directory with cache.")

(defvar spruce-cache-tmp-truedirectory
  (concat spruce-cache-truedirectory "tmp/")
  "Directory with temporary cache.")

(defvar spruce-after-init-truedirectory
  (concat user-emacs-truedirectory "after-init/")
  "Directory with Lisp scripts to be executed when `after-init-hook' is triggered.")

(defvar spruce-after-kill-truedirectory
  (concat user-emacs-truedirectory "after-kill/")
  "Directory with Lisp scripts to be executed when `kill-emacs-hook' is triggered.")

(defvar spruce-after-use-truedirectory
  (concat user-emacs-truedirectory "after-use/")
  "Directory with Lisp scripts to be executed after modes are set.")

(defvar spruce-before-use-truedirectory
  (concat user-emacs-truedirectory "before-use/")
  "Directory with Lisp scripts to be executed before modes are set.")

(defvar spruce-store-truedirectory
  (concat user-emacs-truedirectory "store/")
  "Directory with manually installed packages.")

(defvar spruce-third-truedirectory
  (concat user-emacs-truedirectory "third/")
  "Directory with third party components.")

(defvar spruce-use-truedirectory
  (concat user-emacs-truedirectory "use/")
  "Directory with Lisp scripts for setting modes.")

(defvar spruce-local-truedirectory
  (concat user-emacs-truedirectory "local/")
  "Directory with local contents.")

(defvar spruce-local-after-init-truedirectory
  (concat spruce-local-truedirectory "after-init/")
  "Directory with Lisp scripts to be executed when `after-init-hook' is triggered.")

(defvar spruce-local-after-kill-truedirectory
  (concat spruce-local-truedirectory "after-kill/")
  "Directory with Lisp scripts to be executed when `kill-emacs-hook' is triggered.")

(defvar spruce-local-after-use-truedirectory
  (concat spruce-local-truedirectory "after-use/")
  "Directory with Lisp scripts to be executed after modes are set.")

(defvar spruce-local-before-use-truedirectory
  (concat spruce-local-truedirectory "before-use/")
  "Directory with Lisp scripts to be executed before modes are set.")

(defvar spruce-local-store-truedirectory
  (concat spruce-local-truedirectory "store/")
  "Directory with manually installed packages.")

(defvar spruce-local-third-truedirectory
  (concat spruce-local-truedirectory "third/")
  "Directory with third party components.")

(defvar spruce-local-use-truedirectory
  (concat spruce-local-truedirectory "use/")
  "Directory with Lisp scripts for setting modes.")

(defvar spruce-boot-file
  (concat spruce-local-truedirectory "boot.el")
  "Lisp script execute second.")

(defvar spruce-user-packages nil
  "List with manually installed packages and lists with paths to them.")

(defvar spruce-units (list :all-the-icons-ivy-rich (list (concat spruce-use-truedirectory "ivy/ivy-rich/+all-the-icons-ivy-rich.el"))
			   :all-the-icons-ivy (list (concat spruce-use-truedirectory "ivy/+all-the-icons-ivy.el"))
			   :all-the-icons (list (concat spruce-use-truedirectory "+all-the-icons.el"))
			   :auto-package-update (list (concat spruce-use-truedirectory "+auto-package-update.el"))
			   :buffer-move (list (concat spruce-use-truedirectory "+buffer-move.el"))
			   :c-mode (list (concat spruce-use-truedirectory "+c-mode.el"))
			   :centaur-tabs (list (concat spruce-use-truedirectory "+centaur-tabs.el"))
			   :company-box (list (concat spruce-use-truedirectory "company/+company-box.el"))
			   :company (list (concat spruce-use-truedirectory "company/+company.el"))
			   :compile (list (concat spruce-use-truedirectory "+compile.el"))
			   :counsel (list (concat spruce-use-truedirectory "+counsel.el"))
			   :counsel-projectile (list (concat spruce-use-truedirectory "projectile/+counsel-projectile.el"))
			   :doom-themes (list (concat spruce-use-truedirectory "+doom-themes.el"))
			   :evil-nerd-commenter (list (concat spruce-use-truedirectory "+evil-nerd-commenter.el"))
			   :flycheck (list (concat spruce-use-truedirectory "+flycheck.el"))
			   :format-all (list (concat spruce-use-truedirectory "+format-all.el"))
			   :forth-mode (list (concat spruce-use-truedirectory "+forth-mode.el"))
			   :frame (list (concat spruce-use-truedirectory "+frame.el"))
			   :go-mode (list (concat spruce-use-truedirectory "+go-mode.el"))
			   :goto-line-preview (list (concat spruce-use-truedirectory "+goto-line-preview.el"))
			   :hydra (list (concat spruce-use-truedirectory "+hydra.el"))
			   :ivy (list (concat spruce-use-truedirectory "ivy/+ivy.el"))
			   :ivy-rich (list (concat spruce-use-truedirectory "ivy/ivy-rich/+ivy-rich.el"))
			   :lsp-mode (list (concat spruce-use-truedirectory "lsp/+lsp-mode.el"))
			   :lsp-ui (list (concat spruce-use-truedirectory "lsp/+lsp-ui.el"))
			   :markdown-mode (list (concat spruce-use-truedirectory "+markdown-mode.el"))
			   :neotree (list (concat spruce-use-truedirectory "+neotree.el"))
			   :projectile (list (concat spruce-use-truedirectory "projectile/+projectile.el"))
			   :swiper (list (concat spruce-use-truedirectory "+swiper.el"))
			   :yasnippet (list (concat spruce-use-truedirectory "+yasnippet.el")))
  "List with all available units.")

(defvar spruce-load-units-after-init nil
  "List with units to be executed when `after-init-hook' is triggered.")

(defvar spruce-load-units-after-kill nil
  "List with units to be executed when `kill-emacs-hook' is triggered.")

(defvar spruce-load-units nil
  "List with units to be executed.")

(defun shell-command-to-exit-code-and-string (path &rest args)
  "Execute PATH passing ARGS and return a list with exit code and output."
  (with-temp-buffer
    (list (apply 'call-process path nil (current-buffer) nil args)
	  (buffer-string))))

(defun number-of-cores ()
  "Return the number of processor cores."
  (when (file-exists-p "/proc/cpuinfo")
    (with-temp-buffer
      (insert-file-contents "/proc/cpuinfo")
      (how-many "^processor\\s-*: [0-9]+"))))

(setq package-user-dir (concat user-emacs-truedirectory ".store/")
      custom-file (concat spruce-cache-truedirectory "custom.el")
      package-gnupghome-dir (concat package-user-dir ".gnupg/")
      native-comp-async-jobs-number (or (number-of-cores) 4)
      auto-save-list-file-prefix nil
      make-backup-files nil)

(defun handle-el-path (fun path)
  "Pass Lisp script PATH to FUN.
If PATH is a directory do this with its contents.
Hidden paths and files without an Emacs Lisp extension are skipped."
  (if (file-directory-p path)
      (dolist (suffix (directory-files path nil "^[^\\.]+"))
  	(handle-el-path fun (expand-file-name suffix path)))
    (when (string-match-p "^[^\\.]+\\.el$" (file-name-nondirectory path))
      (funcall fun path))))

(defun load-el-path (path)
  "Execute Lisp script PATH.
If PATH is a directory, do this with its contents.
Hidden paths and files without an Emacs Lisp extension are skipped."
  (if (file-exists-p path)
      (handle-el-path (lambda (el)
			(load (substring el 0 -3)))
		      path)
    (load path)))

(defun handle-load-scripts (fun)
  "Pass all executable Lisp scripts to FUN."
  (dolist (paths (append (list (list (concat user-emacs-truedirectory "init.el")
				     spruce-boot-file))
			 (mapcar (lambda (unit)
				   (let ((scripts (plist-get spruce-units unit)))
				     (unless scripts
				       (error (format "Unit %s not found" unit)))
				     scripts))
				 (append spruce-load-units
					 spruce-load-units-after-init
					 spruce-load-units-after-kill))
			 (mapcar (lambda (unit)
				   (plist-get spruce-user-packages unit))
				 spruce-load-units)))
    (dolist (path paths)
      (handle-el-path fun path))))

(defun spruce-load (units)
  "Execute the contents of the list UNITS."
  (dolist (paths (mapcar (lambda (unit)
			   (let ((scripts (plist-get spruce-units unit)))
			     (unless scripts
			       (error (format "Unit %s not found" unit)))
			     scripts))
			 units))
    (dolist (path paths)
      (load-el-path path))))

(defun spruce-clear ()
  "Remove byte compiled executable Lisp scripts."
  (interactive)
  (handle-load-scripts (lambda (el)
			 (let ((elc (format "%sc" el)))
			   (unless (file-directory-p elc)
			     (delete-file elc))))))

(defun spruce-update ()
  "Update Spruce from git repository."
  (interactive)
  (spruce-clear)
  (if (= (car (shell-command-to-exit-code-and-string
	       "git" "-C" user-emacs-truedirectory "pull"))
	 0)
      (message "Successfully updated")
    (message "Update ended with an error")))

(make-directory spruce-cache-tmp-truedirectory :PARENTS)

(require 'package)
(setq package-archives '(("gnu"   . "http://elpa.gnu.org/packages/")
			 ("melpa" . "http://melpa.org/packages/")))
(package-initialize)

(unless (package-installed-p 'use-package)
  (package-refresh-contents)
  (package-install 'use-package))

(require 'use-package)

(add-hook 'kill-emacs-hook (lambda ()
			     (spruce-load spruce-load-units-after-kill)))

(add-hook 'after-init-hook (lambda ()
			     (spruce-load spruce-load-units-after-init)
			     (when (and (not (file-directory-p custom-file))
					(file-exists-p custom-file))
			       (load-el-path custom-file))))

(when (and (not (file-directory-p spruce-boot-file))
	   (file-exists-p spruce-boot-file))
  (load-el-path spruce-boot-file))

(spruce-load spruce-load-units)

(defun spruce-build ()
  "Byte compile executable Lisp scripts."
  (interactive)
  (handle-load-scripts 'byte-compile-file))

(setq gc-cons-threshold 104857600)

(provide 'init)

;;; init.el ends here
